package dovs.phases;

import dovs.*;
import dovs.analysis.*;
import dovs.node.*;
import dovs.peephole.PeepholeDriver;
import dovs.instructions.*;
import java.io.*;

/**
 *	Final compiler phase to emit the generated code for all classes and
 *	interfaces to jasmin files.
 */
public aspect CodeEmission extends DepthFirstAdapter {
	/** The output stream for the current jasmin file */
	private PrintStream out;
	public static int instructions = 0;

	/** Opens the jasmin file for this class or interface */
	@Override
	public void inASourceFile(ASourceFile sf) {
		String sourcename = sf.getFileName();
		String filename = sourcename.substring(0, sourcename.lastIndexOf('.')) + ".j";
		try {
			out = new PrintStream(new FileOutputStream(filename));
		} catch (IOException e) {
			Errors.fatalErrorMessage(ErrorType.FILE_OPEN_ERROR,
					"Unable to open file " + filename + ": " + e.getMessage());
		}
		out.println(".source " + sourcename.substring(sourcename.lastIndexOf(File.separatorChar) + 1));
	}

	/** Closes the jasmin file for this class or interface */
	@Override @SuppressWarnings("unused")
	public void outASourceFile(ASourceFile sf) {
		out.close();
		
	}

	/** Writes the class header */
	@Override
	public  void inAClassTypeDecl(AClassTypeDecl classd) {
		out.print(".class public ");
		if (classd.isAbstract()) {
			out.print("abstract ");
		}
		if (classd.isFinal()) {
			out.print("final ");
		}
		out.println(classd.getSignature());
		out.println(".super " + classd.getSuper().decl.getSignature());
		for (ANamedType itype : classd.getInterfaces()) {
			out.println(".implements " + itype.decl.getSignature());
		}

		// Jasmin needs the fields first
		for (PDecl member : classd.getMembers()) {
			if (member instanceof AFieldDecl) {
				AFieldDecl fieldd = (AFieldDecl) member;
				out.print(".field " + fieldd.getAccess().accessText() + " ");
				if (fieldd.isStatic()) {
					out.print("static ");
				}
				if (fieldd.isFinal()) {
					out.print("final ");
				}
				out.println("\"" + fieldd.getName().getText() + "\" "
						+ fieldd.getType().getSignature());
			}
		}
	}

	/** Writes the interface header */
	@Override
	public  void inAInterfaceTypeDecl(AInterfaceTypeDecl interfaced) {
		out.println(".interface public abstract " + interfaced.getSignature());
		out.println(".super java/lang/Object");
		for (ANamedType itype : interfaced.getSupers()) {
			out.println(".implements " + itype.decl.getSignature());
		}
	}

	@Override
	public  void inAMethodDecl(AMethodDecl methodd) {
		String sig = methodd.getSignature();
		sig = methodd.getName().getText() + sig.substring(sig.indexOf('('));
		out.println();
		out.print(".method " + methodd.getAccess().accessText() + " ");
		if (methodd.isStatic()) {
			out.print("static ");
		}
		if (methodd.isFinal()) {
			out.print("final ");
		}
		if (methodd.isAbstract()) {
			out.print("abstract ");
		}
		out.println(sig);
		for (ANamedType ttype : methodd.getThrows()) {
			out.println(".throws " + ttype.decl.getSignature());
		}
	}

	@Override @SuppressWarnings("unused")
	public void outAMethodDecl(AMethodDecl methodd) {
		out.println(".end method");
	}

	@Override
	public  void inAConstructorDecl(AConstructorDecl constructord) {
		String sig = constructord.getSignature();
		sig = "<init>" + sig.substring(sig.indexOf('('));
		out.println();
		out.println(".method " + constructord.getAccess().accessText() + " " + sig);
		for (ANamedType ttype : constructord.getThrows()) {
			out.println(".throws " + ttype.decl.getSignature());
		}
	}

	@Override @SuppressWarnings("unused")
	public void outAConstructorDecl(AConstructorDecl constructord) {
		out.println(".end method");
	}

	@Override
	public void caseABody(ABody body) {
		out.println(".limit stack " + body.max_stack);
		out.println(".limit locals " + body.max_locals);
		for (Instruction inst : body.instructions) {
			String asm = inst.toAsm();
			String[] lines = asm.split("\n");
			for (String line : lines) {
				if (line.indexOf("#") != -1) {
					line = line.substring(0, line.indexOf("#"));
				}
				line = line.trim();
				if (!line.isEmpty() && !line.endsWith(":")) {
					instructions++;
				}
			}
			out.println(asm);
		}
	}

}
